#include "preHeader.h"
#include "LocatableObject.h"
#include "Map/MapInstance.h"

static inline AoiActor::Type getAoiActorType(OBJECT_TYPE objType) {
	switch (objType) {
	case TYPE_STATICOBJECT: return AoiActor::Type::SOBJ;
	case TYPE_AURAOBJECT: return AoiActor::Type::AOBJ;
	case TYPE_CREATURE: return AoiActor::Type::CREATURE;
	case TYPE_PLAYER: return AoiActor::Type::PLAYER;
	case TYPE_AUTOPLAYER: return AoiActor::Type::PLAYER;
	default: return AoiActor::Type::OTHER;
	}
}
static inline int getAoiActorFlags(OBJECT_TYPE objType) {
	switch (objType) {
	case TYPE_STATICOBJECT: return 1 << AoiActor::Flag::PRECISE;
	case TYPE_AURAOBJECT: return 1 << AoiActor::Flag::PRECISE;
	case TYPE_CREATURE: return 1 << AoiActor::Flag::PRECISE;
	case TYPE_PLAYER: return 1 << AoiActor::Flag::VIEWER;
	case TYPE_AUTOPLAYER: return 1 << AoiActor::Flag::VIEWER;
	default: return 0;
	}
}

LocatableObject::LocatableObject(OBJECT_TYPE objType)
: Object(objType)
, AoiActor(getAoiActorType(objType), getAoiActorFlags(objType))
, m_pMapInstance(NULL)
, m_instGuid(InstGUID_NULL)
, m_lastUpdateTime(0)
, m_deleteExpireTime(0)
, m_isDisappear(false)
, m_istobeDisappear(false)
, m_objectHookUniqueKey(0)
{
}

LocatableObject::~LocatableObject()
{
	DestructAllObjectHookInfos();
}

WheelTimerMgr *LocatableObject::GetWheelTimerMgr()
{
	return &m_pMapInstance->sWheelTimerMgr;
}

void LocatableObject::Update(uint64 diffTime)
{
	if (m_isDisappear) {
		RemoveFromWorld(m_pMapInstance);
		return;
	}

	CleanObjectHookInfos();

	//Object::Update(diffTime);
}

bool LocatableObject::PushToWorld(MapInstance* pMapInstance)
{
	DBGASSERT(!IsInWorld());
	m_pMapInstance = pMapInstance;
	m_instGuid = pMapInstance->getInstGuid();
	m_lastUpdateTime = GET_SYS_TIME;

	this->InitLuaEnv();

	OnPrePushToWorld();
	if (pMapInstance->PushObject(this)) {
		OnPushToWorld();
		return true;
	}

	return false;
}

void LocatableObject::OutOfWorld(MapInstance* pMapInstance)
{
	DBGASSERT(IsInWorld());
	OnPreLeaveWorld();
	pMapInstance->RemoveObject(this);
	OnLeftWorld();
}

void LocatableObject::RemoveFromWorld(MapInstance* pMapInstance)
{
	DBGASSERT(IsInWorld());
	OnPreLeaveWorld();
	pMapInstance->RemoveObject(this);
	OnLeftWorld();
	this->Delete(pMapInstance);
}

void LocatableObject::Delete(MapInstance* pMapInstance)
{
	DBGASSERT(!IsInWorld());
	DBGASSERT(m_deleteExpireTime == 0);
	m_deleteExpireTime = GET_SYS_TIME + 3000;
	pMapInstance->AddPendingDeleteObject(this);
	OnDelete();
}

void LocatableObject::SetPosition(const vector3f& pos)
{
	if (m_pMapInstance != NULL) {
		if (!m_pMapInstance->IsValidPosition(pos)) {
			return;
		}
	}

	m_position = pos;

	if (IsInWorld()) {
		OnChangePosition();
		m_pMapInstance->ChangeObjectLocation(this);
	}
}

void LocatableObject::SetDirection(const vector3f& dir)
{
	(m_direction = dir).normalize();
}

void LocatableObject::SetOrientation(float o)
{
	(m_direction = {0,0,1}).rotateXZBy(o);
}

void LocatableObject::Relocate(const vector3f& pos)
{
	Relocate(pos, m_direction);
}

void LocatableObject::Relocate(const vector3f& pos, const vector3f& dir)
{
	m_position = pos;
	if (&m_direction != &dir) {
		SetDirection(dir);
	}
	NetPacket pack(SMSG_OBJECT_RELOCATE);
	pack << m_guid << m_position << m_direction;
	SendMessageToSet(pack);
}

void LocatableObject::Relocate(const vector3f& pos, float o)
{
	SetOrientation(o);
	Relocate(pos, m_direction);
}

void LocatableObject::UpdateActive()
{
	uint64 nowSysTime = GET_SYS_TIME;
	this->Update(nowSysTime - m_lastUpdateTime);
	m_lastUpdateTime = nowSysTime;
}

bool LocatableObject::IsDeletable()
{
	if (m_deleteExpireTime < GET_SYS_TIME) {
		return true;
	}
	return false;
}

void LocatableObject::BuildCreatePacketForPlayer(INetPacket& pck, Player* pPlayer)
{
	Object::BuildCreatePacketForPlayer(pck, pPlayer);
	pck << m_instGuid << m_position << m_direction;
}

void LocatableObject::PushMessage(const INetPacket& pck, bool isToSet, bool hasSelf) const
{
	if (isToSet) {
		SendMessageToSet(pck, hasSelf);
	} else if (hasSelf && IsType(TYPE_PLAYER)) {
		((Player*)this)->SendPacket(pck);
	}
}

void LocatableObject::SendMessageToSet(const INetPacket& pck, bool hasSelf) const
{
	if (!IsInWorld()) return;
	AoiActor::ForeachWatcher(AoiActor::PLAYER, [=, &pck](AoiActor* actor) {
		((Player*)actor)->SendPacket(pck);
		return false;
	});
	if (hasSelf && IsType(TYPE_PLAYER)) {
		((Player*)this)->SendPacket(pck);
	}
}

void LocatableObject::SendValueUpdate()
{
	if (IsDirty32()) {
		NetPacket pack(SMSG_VALUE32_UPDATE);
		pack << GetGuid();
		BuildValue32UpdatePacket(pack);
		SendMessageToSet(pack);
		ResetDirty32();
	}
	if (IsDirty64()) {
		NetPacket pack(SMSG_VALUE64_UPDATE);
		pack << GetGuid();
		BuildValue64UpdatePacket(pack);
		SendMessageToSet(pack);
		ResetDirty64();
	}
}

void LocatableObject::Disappear(uint64 delayTime)
{
	m_istobeDisappear = true;
	CreateTimer([=]() {
		m_isDisappear = true;
	}, TimerTypeDisappear, delayTime, 1);
}

void LocatableObject::FastDisappear()
{
	m_isDisappear = true;
	m_istobeDisappear = true;
}

bool LocatableObject::IsVisibleByPlayer(const Player* pPlayer)
{
	switch (m_objType) {
		case TYPE_CREATURE: {
			auto pCreature = static_cast<Creature*>(this);
			return pCreature->IsVisibleByPlayer(pPlayer);
		}
		case TYPE_STATICOBJECT: {
			auto pSObj = static_cast<StaticObject*>(this);
			return pSObj->IsVisibleByPlayer(pPlayer);
		}
		case TYPE_AURAOBJECT: {
			auto pAObj = static_cast<AuraObject*>(this);
			return pAObj->getProto()->isClientVisible;
		}
		default: {
			return true;
		}
	}
}

void LocatableObject::ObjectHookEvent_OnSendMessage(const char* funcName, const LuaTable& args) const
{
	ForeachHookInfo4Event<ObjectHookInfo, ObjectHookEvent, const char*, const LuaTable&>(
		m_objectHookInfos, ObjectHookEvent::OnSendMessage, "OnSendMessage", funcName, args);
}

uint32 LocatableObject::AttachObjectHookInfo(LuaTable&& t)
{
	auto key = NewObjectHookKey();
	auto pObjectHookInfo = new ObjectHookInfo{true};
	HookEvents2bitset(t.get<LuaTable>("events"), pObjectHookInfo->events);
	pObjectHookInfo->t = LuaRef(t.getL(), t.index());
	m_objectHookInfos.emplace(key, pObjectHookInfo);
	if (pObjectHookInfo->events.test((int)ObjectHookEvent::OnSendMessage)) {
		LuaFunc(t.getL(), "AddMethod_OnSendMessage").Call<void, LuaTable&>(t);
	}
	return key;
}

void LocatableObject::DetachObjectHookInfo(uint32 key)
{
	auto itr = m_objectHookInfos.find(key);
	if (itr == m_objectHookInfos.end()) {
		WLOG("DetachObjectHookInfo: Can't find key %d.", key);
		return;
	}
	auto pObjectHookInfo = itr->second;
	if (!pObjectHookInfo->isAvail) {
		WLOG("DetachObjectHookInfo: key %d isn't available.", key);
		return;
	}
	if (pObjectHookInfo->events.test(int(MapHookEvent::OnHookDetach))) {
		LuaFuncs(pObjectHookInfo->t).CallStaticMethod<void>("Cleanup");
	}
	pObjectHookInfo->isAvail = false;
	m_gcObjectHookKeys.push_back(key);
}

void LocatableObject::DestructAllObjectHookInfos()
{
	for (auto& pair : m_objectHookInfos) {
		delete pair.second;
	}
}

void LocatableObject::CleanObjectHookInfos()
{
	for (; !m_gcObjectHookKeys.empty(); m_gcObjectHookKeys.pop_back()) {
		auto key = m_gcObjectHookKeys.back();
		auto itr = m_objectHookInfos.find(key);
		if (itr == m_objectHookInfos.end()) {
			WLOG("CleanObjectHookInfo: Can't find key %d.", key);
			continue;
		}
		auto pObjectHookInfo = itr->second;
		if (pObjectHookInfo->isAvail) {
			WLOG("CleanObjectHookInfo: key %d is available.", key);
			continue;
		}
		m_objectHookInfos.erase(itr);
		delete pObjectHookInfo;
	}
}

void LocatableObject::InitLuaEnv()
{
	LuaTable t(m_pMapInstance->L);
	t.set("blackboard", &m_blackboard);
	m_blackboard.m_variables = LuaRef(t.getL(), -1);

	SubInitLuaEnv();
}

const LuaRef& LocatableObject::GetVariables() const
{
	return m_blackboard.m_variables;
}

AIBlackboard &LocatableObject::GetBlackboard()
{
	return m_blackboard;
}

void LocatableObject::InitBlackboard(lua_State* L)
{
	lua::class_add<Blackboard>(L, "Blackboard");
	lua::class_inh<Blackboard, AIBlackboard>(L);
	lua::class_mem<Blackboard>(L, "variables", &Blackboard::m_variables);
}
